package com.qingzhuge.service;

import com.google.common.collect.Lists;
import com.qingzhuge.annotation.EntityPropertyNull;
import com.qingzhuge.dao.BaseDao;
import com.qingzhuge.entity.BaseBean;
import com.qingzhuge.entity.BaseBriefnessEntity;
import com.qingzhuge.entity.BaseModel;
import com.qingzhuge.entity.FindObjUserCreateBy;
import com.qingzhuge.common.DtoConverter;
import com.qingzhuge.exception.ValidatorException;
import com.qingzhuge.interfaces.IPersistable;
import com.qingzhuge.service.cache.BaseServiceCache;
import com.qingzhuge.utils.PageUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Nonnull;
import javax.persistence.criteria.*;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

import static com.qingzhuge.entity.BaseBean.PAGE_NO;
import static com.qingzhuge.entity.BaseBean.PAGE_SIZE;

/**
 * @author : zero.xiao
 * @description : 业务逻辑层基类
 * @modified :
 */
public class BaseServiceImpl<M extends BaseDao<T, ID>, T extends BaseBean, ID extends Serializable> extends BaseServiceCache<T> implements IBaseService<T, ID> {
    protected M baseDao;

    public BaseServiceImpl() {
        //泛型类作为父类，可以获取子类的所有实际参数的类型
        // 得到泛型父类
        Type genType = this.getClass().getGenericSuperclass();
        //一个泛型类可能有多个泛型形参，比如ClassName<T,K> 这里有两个泛型形参T和K，Class Name<T> 这里只有1个泛型形参T
        if (genType instanceof ParameterizedType) {
            Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
            if (params.length - 1 >= 1) {
                Type param = params[1];
                entityClass = (Class<T>) param;
            }
        }
    }

    @Autowired
    public void setBaseDao(M baseDao) {
        this.baseDao = baseDao;
    }


    @Override
    public Long count() {
        return baseDao.count();
    }

    @Override
    public Long count(T entity) {
        if (null == entity) {
            return count();
        }
        Example<T> example = Example.of(entity);
        return baseDao.count(example);
    }

    @Override
    public boolean exists(T entity) {
        if (null == entity) {
            return false;
        }
        Example<T> example = Example.of(entity);
        return baseDao.exists(example);
    }

    @Override
    public boolean existsById(ID id) {
        if (null == id) {
            return false;
        }
        return baseDao.existsById(id);
    }

    /**
     * 查询entity 属性是否存在，并且是string 类型
     */
    private Field getField(Class<?> clazz, String filed) {
        //向上循环  遍历父类
        for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
            try {
                Field field = clazz.getDeclaredField(filed);
                if (null != field) {
                    return field;
                }
            } catch (NoSuchFieldException e) {
                log.debug("entity name:{},filed name:{} is not", clazz.getSimpleName(), filed);
            }
        }
        return null;
    }

    protected boolean fieldTypeAsString(Class<?> clazz, String filed) {
        Field field = getField(clazz, filed);
        if (null != field && Objects.equals(field.getName(), filed)) {
            return Objects.equals(field.getType().getSimpleName(), "String");
        }
        return false;
    }

    /**
     * 根据实体参数分页查询
     */
    @Override
    public PageUtil<T> query(T entity) {
        Sort sort = Sort.by(Sort.Direction.DESC, IPersistable.ID_FIELDS);
        Integer pageNo = null;
        Integer size = null;
        String orderByField;
        PageRequest page = setPageRequest(pageNo, size,sort);
        Page<T> pageList;
        if (null != entity) {
            pageNo = entity.getPage();
            size = entity.getSize();
            orderByField = entity.getOrderByField();
            if (StringUtils.isNotBlank(orderByField)) {
                boolean typeAsString = fieldTypeAsString(entity.getClass(), orderByField);
                if (typeAsString) {
                    if (null != entity.getAsc() && entity.getAsc()) {
                        sort = Sort.by(Sort.Direction.ASC, orderByField);
                    }
                }
            }
            page = setPageRequest(pageNo, size,sort);
            Specification<T> specification = setQueryPageForSpecification(entity);
            pageList = baseDao.findAll(specification, page);
        }else{
            pageList = baseDao.findAll(page);
        }
        if (pageList.getTotalElements() > 0) {
            new Runnable() {
                @Override
                public void run() {
                    List<T> list = pageList.getContent();
                    log.debug("设置查询缓存 size:{}", pageList.getSize());
                    for (T t : list) {
                        saveCache(t);
                    }
                }
            };
        }

        return pageOf(pageList);
    }

    protected Specification<T> setQueryPageForSpecification(T entity) {
        return new Specification<T>() {
            private static final long serialVersionUID = -8724689778806435095L;

            /**
             * @param root 用于获取属性字段
             * @param query 以用于简单条件查询
             * @param cb 用于构造复杂条件查询
             * @return
             */
            @Override
            public Predicate toPredicate(@Nonnull Root root, @Nonnull CriteriaQuery query, @Nonnull CriteriaBuilder cb) {
                //集合 用于封装查询条件
                List<Predicate> list = new ArrayList<>();
                if (entity instanceof BaseBriefnessEntity) {
                    if (StringUtils.isNotBlank(((BaseBriefnessEntity) entity).getName())) {
                        //全部模糊查询，即%{address}%
                        log.debug("模糊查询 name:{}", ((BaseBriefnessEntity) entity).getName());
                        String fieldName = BaseBriefnessEntity.FIELD_NAME;
                        Expression path = root.get(fieldName).as(String.class);
                        Predicate p1 = cb.like(
                                path,
                                "%" + ((BaseBriefnessEntity) entity).getName() + "%");
                        list.add(p1);
                    }
                }
                if (entity instanceof FindObjUserCreateBy) {
                    Long createBy = ((FindObjUserCreateBy) entity).getCreateBy();
                    if (null != createBy) {
                        Predicate p3 = cb.equal(root.get("createBy").as(Long.class), createBy);
                        list.add(p3);
                    }
                }
                if (StringUtils.isNotBlank(entity.getSearchField())) {
                    boolean typeAsString = fieldTypeAsString(entity.getClass(), entity.getSearchField());
                    if (typeAsString) {
                        BeanPropertyDescriptor getBeanPropertyDescriptor = new BeanPropertyDescriptor(entity).invoke();
                        BeanWrapper srcBean = getBeanPropertyDescriptor.getBeanWrapper();
                        PropertyDescriptor pds = getBeanPropertyDescriptor.getPropertyDescriptor();
                        String pName = pds.getName();
                        Expression expression = root.get(entity.getSearchField()).as(String.class);
                        Object value = srcBean.getPropertyValue(pName);
                        if (null != value && StringUtils.isNotBlank(value.toString())) {
                            Predicate p3 = cb.like(expression, "%" + value + "%");
                            list.add(p3);
                        }
                    } else {
                        BeanPropertyDescriptor descriptor = new BeanPropertyDescriptor(entity).invoke();
                        if(null != descriptor) {
                            Class<?> propertyType = descriptor.getPropertyType();
                            Object value = descriptor.getValue();
                            if (null != value) {
                                Predicate p3 = cb.equal(root.get(entity.getSearchField()).as(propertyType), value);
                                list.add(p3);
                            }
                        }
                    }
                }
                if (entity instanceof BaseModel) {
                    BeanPropertyDescriptor descriptor = new BeanPropertyDescriptor(entity, BaseModel.FIELD_STATUS).invoke();
                    if (null != descriptor) {
                        Class<?> propertyType = descriptor.getPropertyType();
                        Object value = descriptor.getValue();
                        if (null != value) {
                            Predicate p3 = cb.equal(root.get(BaseModel.FIELD_STATUS).as(propertyType), value);
                            list.add(p3);
                        }
                    }
                }
                return cb.and(list.toArray(new Predicate[0]));
            }
        };
    }

    protected PageRequest setPageRequest(Integer pageNo, Integer size, Sort sort){
        if (null == pageNo || pageNo < 1) {
            pageNo = PAGE_NO;
        } else {
            pageNo = pageNo - 1;
        }
        if (null == size || size < 5) {
            size = PAGE_SIZE;
        }
        return PageRequest.of(pageNo, size, sort);
    }

    protected PageUtil<T> pageOf(Page<T> pageList) {
        long totalElements = pageList.getTotalElements();
        if (totalElements > Integer.MAX_VALUE) {
            log.warn("page total max:{}", totalElements);
        }
        return PageUtil.of((int) totalElements,
                pageList.getSize(),
                pageList.getNumber() + 1,
                pageList.getTotalPages(),
                pageList.isFirst(),
                pageList.isLast(),
                pageList.getContent()
        );
    }

    /**
     * 根据Id查询(默认类型T)
     */
    @Override
    public List<T> findAllById(Iterable<ID> ids) {
        List<T> list = Lists.newArrayList();
        if (null != ids) {
            ids.forEach(id -> list.add(findById(id)));
        }
        return list;
    }

    @Override
    public List<T> findAll(T entity) {
        if (null == entity) {
            return findAll();
        }
        Example<T> example = Example.of(entity);
        return baseDao.findAll(example);
    }

    @Override
    public List<T> findAll() {
        return baseDao.findAll();
    }

    @Override
    public List<T> findAll(Sort sort) {
        if (null == sort) {
            return findAll();
        }
        return baseDao.findAll(sort);
    }

    @Override
    public List<T> findAll(T entity, Sort sort) {
        if (null == entity) {
            if (null != sort) {
                return findAll(sort);
            } else {
                return findAll();
            }
        }
        Example<T> example = Example.of(entity);
        return baseDao.findAll(example, sort);
    }

    /**
     * 根据Id查询(默认类型T)
     */
    @Override
    public T findById(ID id) {
        if (null == id) {
            return null;
        }
        T entity = getCache(id.toString());
        if (entity == null) {
            Optional<T> optional = baseDao.findById(id);
            if (optional.isPresent()) {
                entity = optional.get();
            }
            saveCache(entity);
        }
        return entity;
    }

    /**
     * 根据实体参数查询一条记录
     */
    @Override
    public T findOne(T entity) {
        if (null == entity) {
            return null;
        }
        T t = null;
        if (null != entity.getId()) {
            t = getCache(entity.getId().toString());
        }
        if (t == null) {
            Example<T> example = Example.of(entity);
            Optional<T> optional = baseDao.findOne(example);
            if (optional.isPresent()) {
                t = optional.get();
                saveCache(t);
            }
        }
        return t;
    }

    /**
     * 修改/新增
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public T save(T entity) {
        if (null == entity) {
            return null;
        }
        T save;
        if (entity.isNew()) {
            setCreateTime(entity);
            save = baseDao.save(entity);
        } else {
            T dbentity = null;
            Optional<T> optional = baseDao.findById((ID) entity.getId());
            if (optional.isPresent()) {
                dbentity = optional.get();
            }
            if (null != dbentity) {
                copyNonNullProperties(entity, dbentity);
                setUpdateTime(entity);
                save = baseDao.save(dbentity);
            } else {
                throw new ValidatorException("数据不存在");
            }
        }
        deleteCache(save.getId().toString());
        return save;
    }

    /**
     * 将空值的属性从目标实体类中复制到源实体类中
     *
     * @param src    : 要将属性中的空值覆盖的对象(源实体类)
     * @param target :从数据库根据id查询出来的目标对象
     */
    protected void copyNonNullProperties(T src, T target) {
        String[] nullProperties = getNullProperties(src);
        BeanUtils.copyProperties(src, target, nullProperties);
    }

    /**
     * 将为空的properties给找出来,然后返回出来
     */
    private String[] getNullProperties(Object src) {
        BeanWrapper srcBean = new BeanWrapperImpl(src);
        PropertyDescriptor[] pds = srcBean.getPropertyDescriptors();
        Set<String> emptyName = new HashSet<>();
        for (PropertyDescriptor p : pds) {
            String pName = p.getName();
            Object srcValue = srcBean.getPropertyValue(pName);
            getSuperField(entityClass, emptyName, pName, srcValue);
        }
        String[] result = new String[emptyName.size()];
        return emptyName.toArray(result);
    }

    private void getSuperField(Class<? super T> clazz, Set<String> emptyName, String pName, Object srcValue) {
        if (null == clazz) {
            emptyName.add(pName);
        } else {
            try {
                Field field = clazz.getDeclaredField(pName);
                EntityPropertyNull annotation = field.getAnnotation(EntityPropertyNull.class);
                if (srcValue == null && annotation == null) {
                    emptyName.add(pName);
                }
            } catch (NoSuchFieldException e1) {
                getSuperField(clazz.getSuperclass(), emptyName, pName, srcValue);
            }
        }
    }


    /**
     * BaseModel 的子类，都设置更新时间
     *
     * @param entity 需要修改的对象
     */
    private void setUpdateTime(T entity) {
        if (entity instanceof BaseModel) {
            BaseModel bean = (BaseModel) entity;
            bean.setUpdateTime(new Date());
        }
    }

    /**
     * BaseBriefnessEntity 的子类，都设置创建时间
     *
     * @param entity 需要修改的对象
     */
    private void setCreateTime(T entity) {
        if (entity instanceof BaseBriefnessEntity) {
            BaseBriefnessEntity bean = (BaseBriefnessEntity) entity;
            bean.setCreateTime(new Date());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(T entity) {
        if (null == entity) {
            throw new ValidatorException();
        }
        if (null != entity.getId()) {
            deleteCache(entity.getId().toString());
            baseDao.delete(entity);
        } else {
            T t = this.findOne(entity);
            if (null != t) {
                deleteCache(t.getId().toString());
                baseDao.delete(entity);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(ID id) {
        if (null == id) {
            throw new ValidatorException();
        }
        deleteCache(id.toString());
        baseDao.deleteById(id);
    }

    protected class BeanPropertyDescriptor {
        private T entity;
        private String fieldName;
        private BeanWrapper beanWrapper;
        private PropertyDescriptor propertyDescriptor;
        private Class<?> propertyType;
        private Object value;

        BeanPropertyDescriptor(T entity) {
            this.entity = entity;
        }

        BeanPropertyDescriptor(T entity, String fieldName) {
            this.entity = entity;
            this.fieldName = fieldName;
        }

        Class<?> getPropertyType() {
            return propertyType;
        }

        public Object getValue() {
            return value;
        }

        BeanWrapper getBeanWrapper() {
            return beanWrapper;
        }

        PropertyDescriptor getPropertyDescriptor() {
            return propertyDescriptor;
        }

        BeanPropertyDescriptor invoke() {
            if (null == this.entity) {
                return null;
            }
            if (StringUtils.isBlank(this.fieldName)) {
                this.fieldName = entity.getSearchField();
            }
            if (StringUtils.isBlank(this.fieldName)) {
                return null;
            }
            Field field = getField(this.entity.getClass(), this.fieldName);
            if (null == field) {
                return null;
            }
            beanWrapper = new BeanWrapperImpl(entity);
            propertyDescriptor = beanWrapper.getPropertyDescriptor(this.fieldName);
            String pName = propertyDescriptor.getName();
            propertyType = propertyDescriptor.getPropertyType();
            value = beanWrapper.getPropertyValue(pName);
            return this;
        }
    }

    @Override
    public  <D> T dto2Bean(D dto){
        return new DtoConverter(){
            private static final long serialVersionUID = 5043546256012152608L;
        }.doForward(dto, entityClass);
    }
}

